package com.starview.star.compiler.canonical;

import java.util.Map;
import java.util.Map.Entry;

import com.starview.platform.data.type.ContractConstraint;
import com.starview.platform.data.type.DisplayType;
import com.starview.platform.data.type.Freshen;
import com.starview.platform.data.type.IType;
import com.starview.platform.data.type.ITypeConstraint;
import com.starview.platform.data.type.Location;
import com.starview.platform.data.type.Quantifier;
import com.starview.platform.data.type.TypeExp;
import com.starview.platform.data.type.TypeUtils;
import com.starview.platform.data.type.TypeVar;
import com.starview.star.compiler.util.AccessMode;
import com.starview.star.compiler.util.GenSym;
import com.starview.star.compiler.util.Pair;
import com.starview.star.compiler.util.PrettyPrintDisplay;

/**
 * The core type expression in a MethodVariable is an overload type expression:
 * 
 * <Contract> $=> <type>
 * 
 * @author fgm
 * 
 */

@SuppressWarnings("serial")
public class MethodVariable extends Variable
{
  private final IType contract;
  private final String contractName;

  public MethodVariable(Location loc, String name, IType type, String contractName, IType contract)
  {
    super(loc, type, name);
    this.contract = contract;
    this.contractName = contractName;
    assert TypeUtils.isOverloadedType(contract);
  }

  public static MethodVariable create(Location loc, String name, IType type, String contractName, IType contractType)
  {
    return new MethodVariable(loc, name, type, contractName, contractType);
  }

  @Override
  public Variable copy()
  {
    return new MethodVariable(getLoc(), GenSym.genSym(getName()), getType(), contractName, contract);
  }

  @Override
  public Pair<Variable, Map<String, Quantifier>> freshen(Location loc)
  {
    Pair<IType, Map<String, Quantifier>> ff = Freshen.freshen(contract, AccessMode.readOnly, AccessMode.readWrite);

    IType freshened = ff.left;

    IType refreshedContract = TypeUtils.getContract(freshened);
    IType[] refreshedArgs = ((TypeExp) refreshedContract).getTypeArgs();
    ITypeConstraint constraint = new ContractConstraint((TypeExp) TypeUtils.typeExp(contractName, refreshedArgs));

    // Be slightly careful because not all variables in a method belong to the contract.
    bindingLoop: for (Entry<String, Quantifier> entry : ff.right.entrySet()) {
      TypeVar tVar = entry.getValue().getVar();
      for (int ix = 0; ix < refreshedArgs.length; ix++)
        if (refreshedArgs[ix].equals(tVar)) {
          tVar.setConstraint(constraint);
          continue bindingLoop;
        } else if (refreshedArgs[ix] instanceof TypeExp && ((TypeExp) refreshedArgs[ix]).getTypeCon().equals(tVar)) {
          tVar.setConstraint(constraint);
          continue bindingLoop;
        }
    }

    Variable method = new MethodVariable(loc, getName(), TypeUtils.getOverloadedType(freshened), contractName,
        freshened);
    return Pair.pair(method, ff.right);
  }

  public IType getContract()
  {
    return TypeUtils.getContract(contract);
  }

  public String getContractName()
  {
    return contractName;
  }

  @Override
  public void prettyPrint(PrettyPrintDisplay disp)
  {
    disp.append("(");
    super.prettyPrint(disp);
    disp.append(" from ");
    DisplayType.displayContract(disp, getContract());
    disp.append(")");
  }

  @Override
  public boolean equals(Object obj)
  {
    if (obj == this)
      return true;
    if (obj instanceof MethodVariable) {
      MethodVariable other = (MethodVariable) obj;
      return other.getName().equals(getName()) && other.getContract().equals(getContract());
    }
    return false;
  }

  @Override
  public int hashCode()
  {
    return (getName().hashCode() * 43) + getContractName().hashCode();
  }

  @Override
  public boolean isRealVariable()
  {
    return false;
  }
}
